{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "epochs = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 6 - Aprendizado Federado com MNIST usando uma CNN\n",
    "\n",
    "## Melhore o Aprendizado Federado com 10 linhas de código PyTorch + PySyft\n",
    "\n",
    "\n",
    "### Contexto \n",
    "\n",
    "Federated Learning é uma técnica de Machine Learning muito fascinante e inovadora que visa a construção de sistemas que aprendem a partir de dados descentralizados. A idéia é que os dados permaneçam nas mãos de quem os gerou, isto é, seu possuidor (também conhecido como o *Worker*), o que ajuda a melhorar a privacidade e o direito de propriedade, assim o modelo é compartilhado entre os *Workers*. Uma aplicação imediata é, por exemplo, prever a próxima palavra em seu telefone celular quando você escreve texto: você não deseja que os dados usados no treinamento - ou seja, suas mensagens de texto - sejam enviados para um servidor central.\n",
    "\n",
    "\n",
    "A ascensão do Federated Learning está, portanto, fortemente ligada à disseminação da conscientização sobre privacidade de dados, e o GDPR na UE, que faz cumprir a proteção de dados desde maio de 2018, atuou como um catalisador. Para antecipar a regulamentação, grandes companias como Apple ou Google começaram a investir maciçamente nessa tecnologia, especialmente para proteger a privacidade dos usuários de dispositivos móveis, mas não disponibilizaram suas ferramentas. Na OpenMined, acreditamos que qualquer pessoa disposta a conduzir um projeto de Machine Learning deve ser capaz de implementar ferramentas de proteção de privacidade com muito pouco esforço. Criamos ferramentas para criptografar dados em uma única linha [conforme mencionado em nossa postagem no blog](https://blog.openmined.org/training-cnns-using-spdz/) e agora lançamos nosso framework de Aprendizado Federado, que utiliza o nova versão do PyTorch 1.0 para fornecer uma interface intuitiva para criar modelos seguros e escaláveis.\n",
    "\n",
    "Neste tutorial, usaremos diretamente o [exemplo mais básico de treinamento de uma CNN no MNIST usando PyTorch](https://github.com/pytorch/examples/blob/master/mnist/main.py) e mostraremos como é simples implementar o Aprendizado Federado com ele usando nossa biblioteca [PySyft](https://github.com/OpenMined/PySyft/). Analisaremos cada parte do exemplo e sublinharemos o código que foi alterado.\n",
    "\n",
    "Você também pode encontrar esse material em [nosso blog](https://blog.openmined.org/upgrade-to-federated-learning-in-10-lines).\n",
    "\n",
    "Autores:\n",
    "- Théo Ryffel - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "\n",
    "Tradução:\n",
    "- Jeferson Silva - Github: [@jefersonf](https://github.com/jefersonf)\n",
    "\n",
    "**Ok, vamos começar!**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Imports e especificações do modelo\n",
    "\n",
    "Primeiro fazemos os *imports* oficiais"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E então aqueles específicos para o PySyft. Em particular, definimos os workers remotos `alice` e` bob`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy  # <-- NOVO: importe a biblioteca do PySyft\n",
    "hook = sy.TorchHook(torch)  # <-- NOVO: gancho do PyTorch, ou seja, adicione funcionalidades extras para dar suporte ao Federated Learning\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")  # <-- NOVO: define os workers remotos bob\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")  # <-- NOVO: e alice"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Definimos a configuração da tarefa de aprendizagem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Arguments():\n",
    "    def __init__(self):\n",
    "        self.batch_size = 64\n",
    "        self.test_batch_size = 1000\n",
    "        self.epochs = epochs\n",
    "        self.lr = 0.01\n",
    "        self.momentum = 0.5\n",
    "        self.no_cuda = False\n",
    "        self.seed = 1\n",
    "        self.log_interval = 30\n",
    "        self.save_model = False\n",
    "\n",
    "args = Arguments()\n",
    "\n",
    "use_cuda = not args.no_cuda and torch.cuda.is_available()\n",
    "\n",
    "torch.manual_seed(args.seed)\n",
    "\n",
    "device = torch.device(\"cuda\" if use_cuda else \"cpu\")\n",
    "\n",
    "kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Carregamento e envio de dados para os workers\n",
    "Primeiro carregamos os dados e transformamos o conjunto de dados de treinamento em um conjunto de dados federado dividido entre os workers usando o método `.federate`. Este conjunto de dados federado agora é fornecido para um Federated DataLoader. O conjunto de dados de teste permanece inalterado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "federated_train_loader = sy.FederatedDataLoader( # <-- agora é um FederatedDataLoader \n",
    "    datasets.MNIST('../data', train=True, download=True,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ]))\n",
    "    .federate((bob, alice)), # <-- NOVO: distribuímos o dados entre todos os workers, agora é um FederatedDataset\n",
    "    batch_size=args.batch_size, shuffle=True, **kwargs)\n",
    "\n",
    "test_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=False, transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.test_batch_size, shuffle=True, **kwargs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Especificação da CNN\n",
    "Aqui usamos exatamente a mesma CNN que do exemplo oficial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 20, 5, 1)\n",
    "        self.conv2 = nn.Conv2d(20, 50, 5, 1)\n",
    "        self.fc1 = nn.Linear(4*4*50, 500)\n",
    "        self.fc2 = nn.Linear(500, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.conv1(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = F.relu(self.conv2(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = x.view(-1, 4*4*50)\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Defina as funções de treino e teste\n",
    "Para a função de treino, como os *batches* de dados são distribuídos entre `alice` e `bob`, você precisa enviar o modelo para o local certo para cada *batch*. Em seguida, você executa todas as operações remotamente com a mesma sintaxe que você está executando no PyTorch local. Quando terminar, você recupera o modelo atualizado e a perda (*loss*) para buscar melhorias."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(args, model, device, federated_train_loader, optimizer, epoch):\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- agora é um dataset distribuído\n",
    "        model.send(data.location) # <-- NOVO: envie o modelo para o local certo\n",
    "        data, target = data.to(device), target.to(device)\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        model.get() # <-- NOVO: receba o modelo de volta\n",
    "        if batch_idx % args.log_interval == 0:\n",
    "            loss = loss.get() # <-- NOVO: recupere a perda\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,\n",
    "                100. * batch_idx / len(federated_train_loader), loss.item()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A função de teste não muda!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, device, test_loader):\n",
    "    model.eval()\n",
    "    test_loss = 0\n",
    "    correct = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader:\n",
    "            data, target = data.to(device), target.to(device)\n",
    "            output = model(data)\n",
    "            test_loss += F.nll_loss(output, target, reduction='sum').item() # calcule a perda em cada batch\n",
    "            pred = output.argmax(1, keepdim=True) # obtenha o índice de maior probabilidade logarítmica\n",
    "            correct += pred.eq(target.view_as(pred)).sum().item()\n",
    "\n",
    "    test_loss /= len(test_loader.dataset)\n",
    "\n",
    "    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
    "        test_loss, correct, len(test_loader.dataset),\n",
    "        100. * correct / len(test_loader.dataset)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Inicie o treinamento!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "model = Net().to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=args.lr) # TODO momentum não é suportado no momento\n",
    "\n",
    "for epoch in range(1, args.epochs + 1):\n",
    "    train(args, model, device, federated_train_loader, optimizer, epoch)\n",
    "    test(args, model, device, test_loader)\n",
    "\n",
    "if (args.save_model):\n",
    "    torch.save(model.state_dict(), \"mnist_cnn.pt\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! Aqui você treinou um modelo a partir de dados remotos usando o Federated Learning!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Uma última coisa\n",
    "Sei que há uma pergunta que você está ansioso para fazer: **quanto tempo leva o treinamento com Federated Learning comparado ao treinamento com PyTorch normal?**\n",
    "\n",
    "O tempo de computação é na verdade **menos do dobro do tempo** usado para a execução normal do PyTorch! Mais precisamente, leva 1.9 vezes mais tempo, o que é muito pouco se comparado aos recursos que pudemos adicionar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusão\n",
    "\n",
    "Como você observou, modificamos 10 linhas de código para atualizar o exemplo oficial do Pytorch no MNIST para uma configuração real de Aprendizado Federado!\n",
    "\n",
    "Obviamente, existem dezenas de melhorias em que poderíamos pensar. Gostaríamos que o cálculo operasse em paralelo com os *workers* e realizasse a média federada (i.e. federated averaging), atualizando o modelo central a cada `n` *batches* apenas, para reduzir o número de mensagens que usamos para nos comunicarmos entre os *workers*, etc. Essas são características em que estamos trabalhando para tornar o Federated Learning pronto para um ambiente de produção e escreverêmos sobre assim que forem lançadas!\n",
    "\n",
    "Agora você deve poder fazer Aprendizado Federado sozinho! Se você gostou disso e gostaria de se juntar ao movimento em direção à preservação da privacidade, propriedade descentralizada da IA e da cadeia de suprimentos da AI (dados), você pode fazê-lo das seguintes maneiras!\n",
    "\n",
    "\n",
    "### Dê-nos uma estrela em nosso repo do PySyft no GitHub\n",
    "\n",
    "A maneira mais fácil de ajudar nossa comunidade é adicionando uma estrela nos nossos repositórios! Isso ajuda a aumentar a conscientização sobre essas ferramentas legais que estamos construindo.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Veja nossos tutoriais no GitHub!\n",
    "\n",
    "Fizemos tutoriais muito bons para entender melhor como deve ser a Aprendizagem Federada e a proteção de Privacidade, e como estamos construindo as coisas básicas que precisamos para fazer com que isso aconteça.\n",
    "\n",
    "- [Tutoriais do PySyft](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "### Junte-se ao Slack!\n",
    "\n",
    "A melhor maneira de manter-se atualizado sobre os últimos avanços é se juntar à nossa comunidade! \n",
    "\n",
    "- [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### Contribua com o projeto!\n",
    "\n",
    "A melhor maneira de contribuir para a nossa comunidade é se tornando um contribuidor do código! A qualquer momento, você pode acessar a página de *Issues* (problemas) do PySyft no GitHub e filtrar por \"Projetos\". Isso mostrará todas as etiquetas (tags) na parte superior, com uma visão geral de quais projetos você pode participar! Se você não deseja ingressar em um projeto, mas gostaria de codificar um pouco, também pode procurar mais mini-projetos \"independentes\" pesquisando problemas no GitHub marcados como \"good first issue\".\n",
    "\n",
    "- [Etiquetados como Good First Issue](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Doar\n",
    "\n",
    "Se você não tem tempo para contribuir com nossa base de códigos, mas ainda deseja nos apoiar, também pode se tornar um Apoiador em nosso Open Collective. Todas as doações vão para hospedagem na web e outras despesas da comunidade, como hackathons e meetups!\n",
    "\n",
    "[Página do Open Collective do OpenMined](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
